package com.scalar.chiptrack.adaptor.utils;

import java.io.File;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * User: sujan
 * Date: Dec 12, 2008
 * Time: 10:44:38 AM
 */
public class CSVParserImpl implements Parser
{
    private ParserResult parserResult = null;
    private File file = null;
    private int headerRowIdx = 0;
    private int dataStartRowIdx = 1;
    private boolean hasNext = true;
    private char separator;
    private char quotechar;
    private boolean headingsCaseSensitive = false;

    public static final char DEFAULT_SEPARATOR = ',';
    public static final char DEFAULT_QUOTE_CHARACTER = '"';
    

    public CSVParserImpl( File file )
    {
         this(file, DEFAULT_SEPARATOR, DEFAULT_QUOTE_CHARACTER);
    }
    public CSVParserImpl( String filePath, String fileName )
    {
        this( new File ( (filePath != null) ? (filePath + "/" + fileName) : fileName ), DEFAULT_SEPARATOR, DEFAULT_QUOTE_CHARACTER );
    }
    public CSVParserImpl( File file, char separator )
    {
        this(file, separator, DEFAULT_QUOTE_CHARACTER);
    }
    public CSVParserImpl( File file, char separator, char quotechar  )
    {
        this.file = file;
        this.separator = separator;
        this.quotechar = quotechar;
    }

    private int currLineIdx = -1;
    private BufferedReader br = null;

    public ParserResult parse() throws Exception
    {
        LinkedList headings = null;
        List rows = new LinkedList();
        int maxNoOfCols = 0;
        try
        {
            br = new BufferedReader(new FileReader(file));
            String headerRowCsvLine = null;
            String[] csvLineTokens  = null;

            if (headerRowIdx >= 0 )
            {
                for ( int i=0; i <= headerRowIdx; i++ )
                {
                    headerRowCsvLine = getNextLine();
                }

                if ( headerRowCsvLine == null )
                {
                    return null;
                }
                headerRowCsvLine = headerRowCsvLine.replaceAll("\" ,", "\",");

                csvLineTokens = parseLine( headerRowCsvLine );

                if ( csvLineTokens != null )
                {
                    for( int i = 0; i < csvLineTokens.length; i++ )
                    {
                        csvLineTokens[i] = csvLineTokens[i] != null ? csvLineTokens[i].trim() : null;
                    }
                    headings = new LinkedList(Arrays.asList(csvLineTokens));
                }
            }

            String dataCsvLine = null;
            if ( headerRowIdx < dataStartRowIdx )
            {                   
                for ( int i = (currLineIdx+1); i < dataStartRowIdx; i++ )
                {
                    getNextLine();
                }
            }
            else
            {
                currLineIdx = -1;//resetting
                try
                {
                    br.close();
                    br = null;
                } catch ( IOException ignore ){br=null;}
                br = new BufferedReader(new FileReader(file));
                for ( int i=0; i < dataStartRowIdx; i++ )
                {
                    getNextLine();
                }
            }            

            do
            {
                dataCsvLine = getNextLine();

                csvLineTokens = parseLine( dataCsvLine );
                if ( csvLineTokens != null )
                {
                    rows.add( csvLineTokens );
                    if ( csvLineTokens.length > maxNoOfCols )
                    {
                        maxNoOfCols = csvLineTokens.length;
                    }
                }
            }while ( hasNext );
        }
        finally
        {
            try
            {
                br.close();
                br = null;
            }catch ( IOException e )
            {
                br = null;
                System.out.println ( "WARNING!! Could not close buffered reader" );
                e.printStackTrace();
            }
        }

        int noOfRows                = rows.size();
        ParserResultCSVImpl parserResult = new ParserResultCSVImpl();
        Object[][] data = null;
        data = new Object[noOfRows][maxNoOfCols];
        for ( int rowCount = 0; rowCount < noOfRows; rowCount++ )
        {
            String[] tokens = (String[])rows.get( rowCount );
            for ( int dataCol = 0; dataCol < tokens.length; dataCol ++ )
            {
                data[rowCount][dataCol] = tokens[dataCol];
            }
        }
        parserResult.setHeadingsCaseSensitive( headingsCaseSensitive );
        parserResult.setHeadings( headings );
        parserResult.setData( data );
        parserResult.setDataStartLineIdx( dataStartRowIdx );
        this.parserResult = parserResult;
        return parserResult;
    }
    private String getNextLine() throws IOException
    {
        String nextLine = br.readLine();
        if (nextLine == null)
        {
            hasNext = false;
        }
        else
        {
            currLineIdx++;
        }
        return hasNext ? nextLine : null;
    }
    private String[] parseLine ( String csvLine ) throws IOException
    {
        if (csvLine == null || csvLine.trim().length() == 0 )
        {
            return null;
        }
        csvLine = csvLine.replaceAll("\\=", "");
                
        List tokensOnThisLine = new ArrayList();
        StringBuffer sb = new StringBuffer();
        boolean inQuotes = false;
        do {
        	if (inQuotes)
            {
                // continuing a quoted section, reappend newline
                sb.append("\n");
                csvLine = getNextLine();
                if (csvLine == null)
                    break;
            }
            for (int i = 0; i < csvLine.length(); i++)
            {
                char c = csvLine.charAt(i);
                if (c == quotechar)
                {
                    // this gets complex... the quote may end a quoted block, or escape another quote.
                	// do a 1-char lookahead:
                	if( inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
                	    && csvLine.length() > (i+1)  // there is indeed another character to check.
                	    && csvLine.charAt(i+1) == quotechar ) // ..and that char. is a quote also.
                    {
                		// we have two quote chars in a row == one quote char, so consume them both and
                		// put one on the token. we do *not* exit the quoted text.
                		sb.append(csvLine.charAt(i+1));
                		i++;
                	}
                    else
                    {
                		inQuotes = !inQuotes;
                		// the tricky case of an embedded quote in the middle: a,bc"d"ef,g
                		if(i>2 //not on the begining of the line
                				&& csvLine.charAt(i-1) != this.separator //not at the begining of an escape sequence
                				&& csvLine.length()>(i+1) &&
                				csvLine.charAt(i+1) != this.separator //not at the	end of an escape sequence
                		    )
                        {
                			sb.append(c);
                		}
                	}
                }
                else if (c == separator && !inQuotes)
                {
                    tokensOnThisLine.add(sb.toString());
                    sb = new StringBuffer(); // start work on next token
                }
                else
                {
                    sb.append(c);
                }
            }
        }
        while (inQuotes);
        tokensOnThisLine.add(sb.toString());
        return (String[]) tokensOnThisLine.toArray(new String[0]);
    }
    public ParserResult getParserResult()
    {
        return parserResult;
    }

    public int getHeaderRowIdx() {
        return headerRowIdx;
    }

    public void setHeaderRowIdx(int headerRowIdx) {
        this.headerRowIdx = headerRowIdx;
    }

    public int getDataStartRowIdx() {
        return dataStartRowIdx;
    }

    public void setDataStartRowIdx(int dataStartRowIdx) {
        this.dataStartRowIdx = dataStartRowIdx;
    }

    public boolean isHeadingsCaseSensitive() {
        return headingsCaseSensitive;
    }

    public void setHeadingsCaseSensitive(boolean headingsCaseSensitive) {
        this.headingsCaseSensitive = headingsCaseSensitive;
    }
}
